package com.boot.project.core.interceptor;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.boot.project.core.annotation.PermissionRequired;
import com.boot.project.core.constant.RedisConstant;
import com.boot.project.core.enums.code.impl.systemCode.ResourceCode;
import com.boot.project.core.enums.code.impl.systemCode.UserCode;
import com.boot.project.core.enums.common.PermissionLogicalEnum;
import com.boot.project.core.exception.CustomerException;
import com.boot.project.core.model.result.PermissionResult;
import com.boot.project.core.model.system.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @description: 鉴权拦截器
 * @author: tsing
 * @create: 2021-01-24 16:45
 */
@Component
@Slf4j
public class PermissionRequiredInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 1.不是方法就不要鉴权，直接过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        final HandlerMethod handlerMethod = (HandlerMethod) handler;
        final Method method = handlerMethod.getMethod();

        // 2. 如果方法上没有@PermissionRequired注解，就说明不要鉴权，直接过
        if (!method.isAnnotationPresent(PermissionRequired.class)) {
            return true;
        }

        // 3.其他情况需要鉴权
        // 3.1.查看是否登录
        Cookie cookie = ServletUtil.getCookie(request, "token");
        String token = cookie == null ? null : cookie.getValue();
        String userRedisKey = RedisConstant.REDIS_LOGIN_USER_PREFIX + token;
        BoundValueOperations operations = redisTemplate.boundValueOps(userRedisKey);
        if (!redisTemplate.hasKey(userRedisKey)) {
            throw new CustomerException(UserCode.NOT_LOG_IN);
        }

        UserVo redisUser = (UserVo) operations.get();
        PermissionRequired annotation = method.getAnnotation(PermissionRequired.class);
        String[] currentRequiredPermissions = annotation.value();

        // 如果PermissionRequired注解中没有value值，那么就只做是否登录的判断
        if (currentRequiredPermissions.length == 1 && currentRequiredPermissions[0].equals("")) {
            operations.expire(30, TimeUnit.MINUTES);
            request.setAttribute("username", redisUser.getUsername());
            request.setAttribute("userId", redisUser.getId());
            return true;
        }

        // 3.2.如果PermissionRequired注解中有value值，那么接着要做权限的判断
        String roleRedisKey = RedisConstant.REDIS_ROLE_PROFILE_PREFIX + redisUser.getRoleId();
        BoundValueOperations permissionOperations = redisTemplate.boundValueOps(roleRedisKey);
        PermissionResult result = (PermissionResult) permissionOperations.get();
        if (ObjectUtil.isNull(result)) {
            log.info("---------->用户{}鉴权失败，访问路径为{}", redisUser.getUsername(), request.getRequestURI());
            throw new CustomerException(ResourceCode.DO_NOT_HAVE_THIS_PERMISSION);
        }

        Boolean allow;
        String[] hasPermissions = new String[result.getButtons().size()];
        result.getButtons().toArray(hasPermissions);
        // 根据logical进行业务的判断
        if (EnumUtil.equals(annotation.logical(), PermissionLogicalEnum.AND.toString())) {
            allow = checkAnd(hasPermissions, currentRequiredPermissions);
        } else {
            allow = checkOr(hasPermissions, currentRequiredPermissions);
        }

        if (!allow) {
            log.info("---------->用户{}鉴权失败，访问路径为{}", redisUser.getUsername(), request.getRequestURI());
            throw new CustomerException(ResourceCode.DO_NOT_HAVE_THIS_PERMISSION);
        }
        // 重置失效时间
        operations.expire(30, TimeUnit.MINUTES);
        // 将用户信息装到request里
        request.setAttribute("username", redisUser.getUsername());
        request.setAttribute("userId", redisUser.getId());
        return true;
    }

    /**
     * logic为and时的判断方法
     * @param a1 数组1
     * @param a2 数组2
     * @return
     */
    public static boolean checkAnd(String[] a1, String[] a2) {
        for (int i = 0; i < a2.length; i++) {
            if (!ArrayUtil.contains(a1, a2[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * logic为or时的判断方法
     * @param a1 数组1
     * @param a2 数组2
     * @return
     */
    public static boolean checkOr(String[] a1, String[] a2) {
        return ArrayUtil.containsAny(a1, a2);
    }
}
